﻿namespace Bowlus.FrontEnd.RequestRouting.ApplicationRequestRouting
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Linq;

    using Bowlus.Common;

    using Microsoft.Web.Administration;

    public class ArrConfigManager : IRoutingConfigManager, IDisposable
    {
        private readonly ServerManager serverManager;

        private readonly string siteName;

        private static readonly object LockObject = new object();

        public ArrConfigManager(ServerManager serverManager, string siteName)
        {
            this.serverManager = serverManager;
            this.siteName = siteName;
        }

        public string SiteName
        {
            get
            {
                return this.siteName;
            }
        }

        public void Initialize()
        {
            ConfigureArr(this.serverManager, this.siteName);
        }

        public void UpdateApplicationHostsConfiguration(IEnumerable<ApplicationHost> availableApplicationHosts)
        {
            availableApplicationHosts = availableApplicationHosts.ToList();
            Trace.TraceInformation(string.Format("Updating app host configuration for {0} hosts", availableApplicationHosts.Count()));

            var farms = GetFarms(this.serverManager).ToList();

            var existingFarmNames = (from farm in farms select farm.Attributes["name"].Value.ToString()).ToList();
            var availableHostNames = (from appHost in availableApplicationHosts select appHost.DeploymentManagerEndpoint.Address.ToString()).ToList();

            var hostsToBeDeleted = existingFarmNames.Except(availableHostNames);
            var hostsToBeAdded = availableHostNames.Except(existingFarmNames);

            AddFarms(this.serverManager, availableApplicationHosts, hostsToBeAdded);

            DeleteFarms(farms, hostsToBeDeleted);

            this.serverManager.CommitChanges();
        }

        public void Dispose()
        {
            if (this.serverManager != null)
            {
                this.serverManager.Dispose();
            }
        }

        private static void DeleteFarms(List<ConfigurationElement> farms, IEnumerable<string> farmsToBeDeleted)
        {
            var toBeDeleted = from farmName in farmsToBeDeleted
                              join arrFarm in farms on farmName equals arrFarm.GetAttributeValue("name")
                              select arrFarm;

            foreach (var arrFarmToDelete in toBeDeleted)
            {
                farms.Remove(arrFarmToDelete);
            }
        }

        private static void AddFarms(ServerManager arrServerManager, IEnumerable<ApplicationHost> availableApplicationHosts, IEnumerable<string> farmsToBeAdded)
        {
            farmsToBeAdded = farmsToBeAdded.ToList();

            Trace.TraceInformation(string.Format("Adding {0} farms.", farmsToBeAdded.Count()));

            var toBeAdded = from hostName in farmsToBeAdded
                            join applicationHost in availableApplicationHosts on hostName equals applicationHost.DeploymentManagerEndpoint.Address.ToString()
                            select
                                new ServerFarm
                                    {
                                        Name = hostName,
                                        Servers =
                                            new Collection<ApplicationHost> { applicationHost }
                                    };

            // Add first
            foreach (var farm in toBeAdded)
            {
                foreach (var serverFarmServer in farm.Servers)
                {
                    // One farm for each AppHost, having one server in the farm
                    var arrFarmElement = CreateArrFarmConfigurationElement(
                        arrServerManager, serverFarmServer.DeploymentManagerEndpoint.Address.ToString());
                    var arrFarmServers = arrFarmElement.GetCollection();
                    var arrFarmServer = arrFarmServers.CreateElement("server");
                    arrFarmServer.SetAttributeValue("address", serverFarmServer.DeploymentManagerEndpoint.Address.ToString());
                    arrFarmServer.SetAttributeValue("enabled", true);
                    arrFarmServers.Add(arrFarmServer);
                    arrFarmServer.GetChildElement("applicationRequestRouting").SetAttributeValue(
                        "httpPort", serverFarmServer.DeploymentManagerEndpoint.Port);

                    Trace.WriteLine(
                        string.Format(
                            "Adding farm server {0}:{1}",
                            serverFarmServer.DeploymentManagerEndpoint.Address,
                            serverFarmServer.DeploymentManagerEndpoint.Port));
                }
            }
        }

        private static IEnumerable<ConfigurationElement> GetFarms(ServerManager arrServerManager)
        {
            var config = arrServerManager.GetApplicationHostConfiguration();

            var farmsSection = config.GetSection("webFarms");
            return farmsSection.GetCollection();
        }

        public void UpdateRoutingRules(IList<TenantDeployment> tenantDeployments)
        {
            /*
              <?xml version="1.0" encoding="utf-8"?>
              <configuration>
                <system.webserver>
                  <rewrite>
                    <rules>
                      <rule name="host name" stopprocessing="true">
                        <match url=".*" />
                        <conditions logicalgrouping="matchall">
                          <add input="{http_host}" pattern="www\.yourdomain\.com" />
                        </conditions>
                        <action type="rewrite" url="index.php" />
                      </rule>
                    </rules>
                  </rewrite>
                </system.webserver>
              </configuration>
             */

            lock (LockObject)
            {
                Trace.TraceInformation("Updating rules.");
                var config = this.serverManager.GetApplicationHostConfiguration();
                var rules = config.GetSection("system.webServer/rewrite/rules");

                // we will need to find the new tenant deployments in the existing config
                // and remove them first. Not all of the config. THen add the new ones to the config.
                var allRules = rules.GetCollection();
                var tenantRulesToRemove = (from rule in allRules
                                           let hostName = rule.GetAttributeValue("name")
                                           join tenantDeployment in tenantDeployments on hostName equals
                                               tenantDeployment.HostName
                                           select hostName).ToList();

                foreach (var rule in allRules)
                {
                    if (tenantRulesToRemove.Any(h => h.Equals(rule.GetAttributeValue("name"))))
                    {
                        rule.Delete();
                    }
                }

                foreach (var td in tenantDeployments)
                {
                    try
                    {
                        var rule = rules.GetCollection().CreateElement("rule");
                        rule.SetAttributeValue("name", td.HostName);
                        rule.SetAttributeValue("stopProcessing", true);
                        rules.GetCollection().Add(rule);
                        rule.GetChildElement("match").SetAttributeValue("url", ".*");
                        rule.GetChildElement("action").SetAttributeValue("type", "Rewrite");
                        rule.GetChildElement("action").SetAttributeValue("url", "http://" + td.AppHostInstanceIp + "/{R:0}");

                        var conditions = rule.GetChildElement("conditions");

                        var condition = conditions.GetCollection().CreateElement("add");
                        condition.SetAttributeValue("input", "{HTTP_HOST}");
                        condition.SetAttributeValue("pattern", td.HostName);
                        conditions.GetCollection().Add(condition);
                    }
                    catch (Exception ex)
                    {
                        Trace.WriteLine(string.Format("Exception. Assuming rewrite rule already exists. Exception was {0}", ex));
                    }
                }

                this.serverManager.CommitChanges();
            }
        }

        public void ClearConfiguration()
        {
            var farms = this.serverManager.GetApplicationHostConfiguration().GetSection("webFarms").GetCollection();
            farms.Clear();

            var rules =
                this.serverManager.GetApplicationHostConfiguration().GetSection("system.webServer/rewrite/rules").
                    GetCollection();
            rules.Clear();

            this.serverManager.CommitChanges();
        }

        public IEnumerable<string> GetApphostAddresses()
        {
            var farms =
                this.serverManager.GetApplicationHostConfiguration().GetSection("webFarms").GetCollection();

            var hosts =
                farms.SelectMany(wf => wf.GetCollection()).Select(h => (string) h.GetAttributeValue("address"));

            return hosts;
        }

        private static void ConfigureArr(ServerManager arrServerManager, string siteName)
        {
            var site = arrServerManager.Sites[siteName];
            var siteApplication = site.Applications.First();

            var appPool = arrServerManager.ApplicationPools[siteApplication.ApplicationPoolName];
            appPool.ManagedPipelineMode = ManagedPipelineMode.Classic;
            appPool.ProcessModel.LoadUserProfile = false;

            var localIpv4Address = Helper.HostIpAddress;

            var httpBindings = site.Bindings.Where(binding => binding.Protocol.Equals("http"));

            foreach (var httpBinding in httpBindings)
            {
                httpBinding["bindingInformation"] = string.Format("{0}:80:", localIpv4Address);
            }

            arrServerManager.CommitChanges();
        }

        private static ConfigurationElement CreateArrFarmConfigurationElement(ServerManager arrServerManager, string farmName)
        {
            var config = arrServerManager.GetApplicationHostConfiguration();

            var farmsSection = config.GetSection("webFarms");
            var farms = farmsSection.GetCollection();

            var farm = farms.CreateElement("webFarm");
            farm.SetAttributeValue("name", farmName);
            farm.SetAttributeValue("enabled", "true");
            farms.Add(farm);

            // We are currently using a single server
            // farm.GetChildElement("applicationRequestRouting").GetChildElement("affinity").SetAttributeValue("useCookie", true);
            Trace.WriteLine("will set load balancing algorithm");
            farm.GetChildElement("applicationRequestRouting").GetChildElement("loadBalancing").SetAttributeValue("algorithm", "WeightedRoundRobin");

            Trace.WriteLine(string.Format("CreateArrFarmConfigurationElement - will return {0}", farm));

            return farm;
        }
    }
}
